//
// Created by jmdvirus on 2021/6/10 上午11:49.
//
//     ******          *             *--*
//       ||            *             |
//       ||  *--*--*---*  * *-********--*
//   *  //   *  *  *   *\ / ||  |  |    |
//    **    *  *  *---* *  *   *--* *--*
//

#include "CMBacktraceSelf.h"

#include <assert.h>
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>

#if (!defined(__cplusplus)) && (!defined(NO_CPP_DEMANGLE))
#define NO_CPP_DEMANGLE
#endif

#ifndef NO_CPP_DEMANGLE
#include <cxxabi.h>
#ifdef __cplusplus
using __cxxabiv1::__cxa_demangle;
#endif
#endif

void CMBacktraceSelf::print_backtrace_normal(void **array, int size)
{
    printf("back address count [%d]\n", size);
    char **result = backtrace_symbols(array, size);
    for (int i = 0; i < size; i++)
    {
        printf("%02d: %s\n", i, result[i]);
    }
}

char **CMBacktraceSelf::backtrace_symbols_self(void *const *array, int size)
{
#define WORD_WIDTH 8
    Dl_info info[size];
    int     status[size];
    int     cnt;
    size_t  total = 0;
    char ** result;

    /* Fill in the information we can get from `dladdr'.  */
    for (cnt = 0; cnt < size; ++cnt)
    {
        status[cnt] = dladdr(array[cnt], &info[cnt]);
        if (status[cnt] && info[cnt].dli_fname &&
            info[cnt].dli_fname[0] != '\0')
        {
            /* We have some info, compute the length of the string which will be
            "<file-name>(<sym-name>) [+offset].  */
            total += (strlen(info[cnt].dli_fname ?: "") +
                      (info[cnt].dli_sname
                           ? strlen(info[cnt].dli_sname) + 3 + WORD_WIDTH + 3
                           : 1) +
                      WORD_WIDTH + 5);
        }
        else
        {
            total += 5 + WORD_WIDTH;
        }
    }

    /* Allocate memory for the result.  */
    result = (char **)malloc(size * sizeof(char *) + total);
    if (result != NULL)
    {
        char *last = (char *)(result + size);

        for (cnt = 0; cnt < size; ++cnt)
        {
            result[cnt] = last;

            if (status[cnt] && info[cnt].dli_fname &&
                info[cnt].dli_fname[0] != '\0')
            {
                char buf[20];

                if (array[cnt] >= (void *)info[cnt].dli_saddr)
                    sprintf(buf, "+%#lx",
                            (unsigned long)((char *)array[cnt] -
                                            (char *)info[cnt].dli_saddr));
                else
                    sprintf(buf, "-%#lx",
                            (unsigned long)((char *)info[cnt].dli_saddr -
                                            (char *)array[cnt]));

                last +=
                    1 + sprintf(last, "%s%s%s%s%s[%p]",
                                info[cnt].dli_fname ?: "",
                                info[cnt].dli_sname ? "(" : "",
                                info[cnt].dli_sname ?: "",
                                info[cnt].dli_sname ? buf : "",
                                info[cnt].dli_sname ? ") " : " ", array[cnt]);
            }
            else
                last += 1 + sprintf(last, "[%p]", array[cnt]);
        }
        assert(last <= (char *)result + size * sizeof(char *) + total);
    }

    return result;
}

int CMBacktraceSelf::addr_to_name(void *addr, Dl_info *dl_info, char *name)
{
    if (!dladdr(addr, dl_info))
        return -1;
    const char *sname = dl_info->dli_sname;
    int         status;
    char *      tmp = __cxa_demangle(sname, NULL, 0, &status);
    if (status == 0 && tmp)
    {
        sname = tmp;
    }
    if (sname)
    {
        strncpy(name, sname, 256);
    }
    if (tmp)
        free(tmp);
    return 0;
}

char **CMBacktraceSelf::print_call_stack(ucontext_t *uc, int *size)
{
    int     i              = 0;
    void ** frame_pointer  = (void **)NULL;
    void *  return_address = NULL;
    void *  lr_address     = NULL;
    Dl_info dl_info;

#ifdef __AMD64__
    frame_pointer  = (void **)uc->uc_mcontext.gregs[REG_RIP];
    return_address = (void *)uc->uc_mcontext.gregs[REG_RSP];
    lr_address     = (void *)uc->uc_mcontext.gregs[REG_EFL];
#elif __ARM64__
    frame_pointer  = (void **)uc->uc_mcontext.regs[16];
    return_address = (void *)uc->uc_mcontext.pc;
    lr_address     = (void *)uc->uc_mcontext.regs[17];
#else
    frame_pointer  = (void **)uc->uc_mcontext.arm_fp;
    return_address = (void *)uc->uc_mcontext.arm_pc;
    lr_address     = (void *)uc->uc_mcontext.arm_lr;
#endif
    printf("frame_pointer = %p, %p, pc = %p, lr = %p\n", frame_pointer,
           *(frame_pointer - 1), return_address, lr_address);

    char return_name[512] = {0};

    char **result   = (char **)calloc(1, 20 * sizeof(char *) + 20 * 513);
    char * save_pos = (char *)(result + 20 * sizeof(char *));

    result[i] = save_pos;

    int st = addr_to_name(lr_address, &dl_info, return_name);
    if (st >= 0)
    {
        sprintf(result[i], "%02d: %p <%s + %lu> (%s)", i, lr_address,
                return_name,
                (unsigned long)lr_address - (unsigned long)dl_info.dli_saddr,
                dl_info.dli_fname);
        save_pos += 512 + 1;
        i++;
    }

    printf("\nStack trace:\n");

    while (frame_pointer && return_address)
    {
        if (i >= 20)
        {
            break;
        }
        result[i] = save_pos;
        int st    = addr_to_name(return_address, &dl_info, return_name);
        if (st >= 0)
        {

            printf("%02d: %p <%s + %lu> (%s)\n", i, return_address, return_name,
                   (unsigned long)return_address -
                       (unsigned long)dl_info.dli_saddr,
                   dl_info.dli_fname);
            sprintf(result[i], "%02d: %p <%s + %lu> (%s)", i, return_address,
                    return_name,
                    (unsigned long)return_address -
                        (unsigned long)dl_info.dli_saddr,
                    dl_info.dli_fname);
            i++;
            save_pos += (i + 1) * 512 + 1;
            if (dl_info.dli_sname && !strcmp(dl_info.dli_sname, "main"))
            {
                break;
            }
        }

        return_address = frame_pointer[-1];
        frame_pointer  = (void **)frame_pointer[-3];

#if 0
        if (lr_address)
        {
            st = addr_to_name(lr_address, &dl_info, return_name);
            if (st >= 0)
            {
                printf("lr_address <%p, %s>\n", lr_address, return_name);
            }
        }
        lr_address = frame_pointer[-2];
#endif
    }
    if (size)
    {
        *size = i;
    }
    printf("Stack strace end [%d]\n", i);
    return result;
}
